# Copyright (c) 2020,2021 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder.  You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Copyright (c) 2009 The Hewlett-Packard Development Company
# Copyright (c) 2017 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os.path
import re
import sys

from code_formatter import code_formatter
from grammar import Grammar, ParseError

import slicc.ast as ast
import slicc.util as util
from slicc.symbols import SymbolTable


class SLICC(Grammar):
    def __init__(
        self, filename, base_dir, verbose=False, traceback=False, **kwargs
    ):
        self.protocol = None
        self.traceback = traceback
        self.verbose = verbose
        self.symtab = SymbolTable(self)
        self.base_dir = base_dir

        try:
            self.decl_list = self.parse_file(filename, **kwargs)
        except ParseError as e:
            if not self.traceback:
                sys.exit(str(e))
            raise

    def currentLocation(self):
        return util.Location(
            self.current_source, self.current_line, no_warning=not self.verbose
        )

    def codeFormatter(self, *args, **kwargs):
        code = code_formatter(*args, **kwargs)
        code["protocol"] = self.protocol
        return code

    def process(self):
        self.decl_list.generate()

    def writeCodeFiles(self, code_path, includes):
        self.symtab.writeCodeFiles(code_path, includes)

    def writeHTMLFiles(self, html_path):
        self.symtab.writeHTMLFiles(html_path)

    def files(self):
        f = set(["Types.hh"])

        f |= self.decl_list.files()

        return f

    t_ignore = "\t "

    # C or C++ comment (ignore)
    def t_c_comment(self, t):
        r"/\*(.|\n)*?\*/"
        t.lexer.lineno += t.value.count("\n")

    def t_cpp_comment(self, t):
        r"//.*"

    # Define a rule so we can track line numbers
    def t_newline(self, t):
        r"\n+"
        t.lexer.lineno += len(t.value)

    reserved = {
        "protocol": "PROTOCOL",
        "include": "INCLUDE",
        "global": "GLOBAL",
        "machine": "MACHINE",
        "in_port": "IN_PORT",
        "out_port": "OUT_PORT",
        "action": "ACTION",
        "transition": "TRANS",
        "structure": "STRUCT",
        "external_type": "EXTERN_TYPE",
        "enumeration": "ENUM",
        "state_declaration": "STATE_DECL",
        "peek": "PEEK",
        "stall_and_wait": "STALL_AND_WAIT",
        "wakeup_port": "WAKEUP_PORT",
        "enqueue": "ENQUEUE",
        "check_allocate": "CHECK_ALLOCATE",
        "check_next_cycle": "CHECK_NEXT_CYCLE",
        "check_stop_slots": "CHECK_STOP_SLOTS",
        "check_on_cache_probe": "CHECK_PROBE",
        "static_cast": "STATIC_CAST",
        "if": "IF",
        "is_valid": "IS_VALID",
        "is_invalid": "IS_INVALID",
        "else": "ELSE",
        "return": "RETURN",
        "void": "VOID",
        "new": "NEW",
        "OOD": "OOD",
        "defer_enqueueing": "DEFER_ENQUEUEING",
    }

    literals = ":[]{}(),="

    tokens = [
        "EQ",
        "NE",
        "LT",
        "GT",
        "LE",
        "GE",
        "LEFTSHIFT",
        "RIGHTSHIFT",
        "NOT",
        "AND",
        "OR",
        "PLUS",
        "DASH",
        "STAR",
        "SLASH",
        "MOD",
        "INCR",
        "DECR",
        "DOUBLE_COLON",
        "SEMI",
        "ASSIGN",
        "DOT",
        "IDENT",
        "LIT_BOOL",
        "FLOATNUMBER",
        "NUMBER",
        "STRING",
        "AMP",
        "CONST",
    ]
    tokens += reserved.values()

    t_EQ = r"=="
    t_NE = r"!="
    t_LT = r"<"
    t_GT = r">"
    t_LE = r"<="
    t_GE = r">="
    t_LEFTSHIFT = r"<<"
    t_RIGHTSHIFT = r">>"
    t_NOT = r"!"
    t_AND = r"&&"
    t_OR = r"\|\|"
    t_PLUS = r"\+"
    t_DASH = r"-"
    t_STAR = r"\*"
    t_AMP = r"&"
    t_CONST = r"const"
    t_SLASH = r"/"
    t_MOD = r"%"
    t_DOUBLE_COLON = r"::"
    t_SEMI = r";"
    t_ASSIGN = r":="
    t_DOT = r"\."
    t_INCR = r"\+\+"
    t_DECR = r"--"

    precedence = (
        ("left", "INCR", "DECR"),
        ("left", "OR"),
        ("left", "AND"),
        ("left", "EQ", "NE"),
        ("left", "LT", "GT", "LE", "GE"),
        ("left", "RIGHTSHIFT", "LEFTSHIFT"),
        ("left", "PLUS", "DASH"),
        ("left", "STAR", "SLASH", "MOD"),
        ("right", "NOT", "UMINUS"),
    )

    def t_IDENT(self, t):
        r"[a-zA-Z_][a-zA-Z_0-9]*"
        if t.value == "true":
            t.type = "LIT_BOOL"
            t.value = True
            return t

        if t.value == "false":
            t.type = "LIT_BOOL"
            t.value = False
            return t

        # Check for reserved words
        t.type = self.reserved.get(t.value, "IDENT")
        return t

    def t_FLOATNUMBER(self, t):
        "[0-9]+[.][0-9]+"
        try:
            t.value = float(t.value)
        except ValueError:
            raise ParseError("Illegal float", t)
        return t

    def t_NUMBER(self, t):
        r"[0-9]+"
        try:
            t.value = int(t.value)
        except ValueError:
            raise ParseError("Illegal number", t)
        return t

    def t_STRING1(self, t):
        r'\"[^"\n]*\"'
        t.type = "STRING"
        t.value = t.value[1:-1]
        return t

    def t_STRING2(self, t):
        r"\'[^'\n]*\'"
        t.type = "STRING"
        t.value = t.value[1:-1]
        return t

    def p_file(self, p):
        "file : decls"
        p[0] = p[1]

    def p_empty(self, p):
        "empty :"

    def p_decls(self, p):
        "decls : declsx"
        p[0] = ast.DeclListAST(self, p[1])

    def p_declsx__list(self, p):
        "declsx : decl declsx"
        if isinstance(p[1], ast.DeclListAST):
            decls = p[1].decls
        elif p[1] is None:
            decls = []
        else:
            decls = [p[1]]
        p[0] = decls + p[2]

    def p_declsx__none(self, p):
        "declsx : empty"
        p[0] = []

    def p_decl__protocol(self, p):
        "decl : PROTOCOL STRING SEMI"
        if self.protocol:
            msg = "Protocol can only be set once! Error at %s:%s\n" % (
                self.current_source,
                self.current_line,
            )
            raise ParseError(msg)
        self.protocol = p[2]
        p[0] = None

    def p_decl__include(self, p):
        "decl : INCLUDE STRING SEMI"
        dirname = os.path.dirname(self.current_source)
        if os.path.exists(os.path.join(dirname, p[2])):
            filename = os.path.join(dirname, p[2])
        else:
            filename = os.path.join(self.base_dir, p[2])
        p[0] = self.parse_file(filename)

    def p_decl__machine0(self, p):
        "decl : MACHINE '(' enumeration ')' ':' obj_decls '{' decls '}'"
        p[0] = ast.MachineAST(self, p[3], [], p[7], p[9])

    def p_decl__machine1(self, p):
        "decl : MACHINE '(' enumeration pairs ')' ':' obj_decls '{' decls '}'"
        p[0] = ast.MachineAST(self, p[3], p[4], p[7], p[9])

    def p_decl__action(self, p):
        "decl : ACTION '(' ident pairs ')' statements"
        p[0] = ast.ActionDeclAST(self, p[3], p[4], p[6])

    def p_decl__in_port(self, p):
        "decl : IN_PORT '(' ident ',' type ',' var pairs ')' statements"
        p[0] = ast.InPortDeclAST(self, p[3], p[5], p[7], p[8], p[10])

    def p_decl__out_port(self, p):
        "decl : OUT_PORT '(' ident ',' type ',' var pairs ')' SEMI"
        p[0] = ast.OutPortDeclAST(self, p[3], p[5], p[7], p[8])

    def p_decl__trans0(self, p):
        "decl : TRANS '(' idents ',' idents ',' ident_or_star ')' idents"
        p[0] = ast.TransitionDeclAST(self, [], p[3], p[5], p[7], p[9])

    def p_decl__trans1(self, p):
        "decl : TRANS '(' idents ',' idents ')' idents"
        p[0] = ast.TransitionDeclAST(self, [], p[3], p[5], None, p[7])

    def p_decl__trans2(self, p):
        "decl : TRANS '(' idents ',' idents ',' ident_or_star ')' idents idents"
        p[0] = ast.TransitionDeclAST(self, p[9], p[3], p[5], p[7], p[10])

    def p_decl__trans3(self, p):
        "decl : TRANS '(' idents ',' idents ')' idents idents"
        p[0] = ast.TransitionDeclAST(self, p[7], p[3], p[5], None, p[8])

    def p_decl__extern0(self, p):
        "decl : EXTERN_TYPE '(' type pairs ')' SEMI"
        p[4]["external"] = "yes"
        p[0] = ast.TypeDeclAST(self, p[3], p[4], [])

    def p_decl__global(self, p):
        "decl : GLOBAL '(' type pairs ')' '{' type_members '}'"
        p[4]["global"] = "yes"
        p[0] = ast.TypeDeclAST(self, p[3], p[4], p[7])

    def p_decl__struct(self, p):
        "decl : STRUCT '(' type pairs ')' '{' type_members '}'"
        p[0] = ast.TypeDeclAST(self, p[3], p[4], p[7])

    def p_decl__enum(self, p):
        "decl : ENUM '(' type pairs ')' '{' type_enums   '}'"
        p[4]["enumeration"] = "yes"
        p[0] = ast.EnumDeclAST(self, p[3], p[4], p[7])

    def p_decl__state_decl(self, p):
        "decl : STATE_DECL '(' type pairs ')' '{' type_states   '}'"
        p[4]["enumeration"] = "yes"
        p[4]["state_decl"] = "yes"
        p[0] = ast.StateDeclAST(self, p[3], p[4], p[7])

    # Type fields
    def p_obj_decls__list(self, p):
        "obj_decls : obj_decl obj_decls"
        p[0] = [p[1]] + p[2]

    def p_obj_decls__empty(self, p):
        "obj_decls : empty"
        p[0] = []

    def p_type_members__list(self, p):
        "type_members : type_member type_members"
        p[0] = [p[1]] + p[2]

    def p_type_members__empty(self, p):
        "type_members : empty"
        p[0] = []

    def p_type_member__0(self, p):
        """type_member : obj_decl
        | func_decl
        | func_def"""
        p[0] = p[1]

    # Member / Variable declarations
    def p_decl__obj_decl(self, p):
        "decl : obj_decl"
        p[0] = p[1]

    def p_obj_decl__0(self, p):
        "obj_decl : type ident pairs SEMI"
        p[0] = ast.ObjDeclAST(self, p[1], p[2], p[3], None, False)

    def p_obj_decl__1(self, p):
        "obj_decl : type STAR ident pairs SEMI"
        p[0] = ast.ObjDeclAST(self, p[1], p[3], p[4], None, True)

    def p_obj_decl__2(self, p):
        "obj_decl : type ident ASSIGN expr SEMI"
        p[0] = ast.ObjDeclAST(
            self, p[1], p[2], ast.PairListAST(self), p[4], False
        )

    def p_obj_decl__3(self, p):
        "obj_decl : type STAR ident ASSIGN expr SEMI"
        p[0] = ast.ObjDeclAST(
            self, p[1], p[3], ast.PairListAST(self), p[5], True
        )

    # Function definition and declaration
    def p_decl__func_decl(self, p):
        "decl : func_decl"
        p[0] = p[1]

    def p_func_decl__0(self, p):
        """func_decl :  void ident '(' params ')' pairs SEMI
        | type ident '(' params ')' pairs SEMI"""
        p[0] = ast.FuncDeclAST(self, p[1], p[2], p[4], p[6], None)

    def p_func_decl__1(self, p):
        """func_decl :  void ident '(' types ')' pairs SEMI
        | type ident '(' types ')' pairs SEMI"""
        p[0] = ast.FuncDeclAST(self, p[1], p[2], p[4], p[6], None)

    def p_decl__func_def(self, p):
        "decl : func_def"
        p[0] = p[1]

    def p_func_def__0(self, p):
        """func_def : void ident '(' params ')' pairs statements
        | type ident '(' params ')' pairs statements"""
        p[0] = ast.FuncDeclAST(self, p[1], p[2], p[4], p[6], p[7])

    # Enum fields
    def p_type_enums__list(self, p):
        "type_enums : type_enum type_enums"
        p[0] = [p[1]] + p[2]

    def p_type_enums__empty(self, p):
        "type_enums : empty"
        p[0] = []

    def p_type_enum(self, p):
        "type_enum : ident pairs SEMI"
        p[0] = ast.TypeFieldEnumAST(self, p[1], p[2])

    # States
    def p_type_states__list(self, p):
        "type_states : type_state type_states"
        p[0] = [p[1]] + p[2]

    def p_type_states__empty(self, p):
        "type_states : empty"
        p[0] = []

    def p_type_state(self, p):
        "type_state : ident ',' enumeration pairs SEMI"
        p[0] = ast.TypeFieldStateAST(self, p[1], p[3], p[4])

    # Formal Param
    def p_params__many(self, p):
        "params : param ',' params"
        p[0] = [p[1]] + p[3]

    def p_params__one(self, p):
        "params : param"
        p[0] = [p[1]]

    def p_params__none(self, p):
        "params : empty"
        p[0] = []

    def p_param(self, p):
        "param : type ident"
        p[0] = ast.FormalParamAST(self, p[1], p[2])

    def p_param__pointer(self, p):
        "param : type STAR ident"
        p[0] = ast.FormalParamAST(self, p[1], p[3], None, "PTR")

    def p_param__ref(self, p):
        "param : type AMP ident"
        p[0] = ast.FormalParamAST(self, p[1], p[3], None, "REF")

    def p_param__const_ref(self, p):
        "param : CONST type AMP ident"
        p[0] = ast.FormalParamAST(self, p[1], p[3], None, "CONST_REF")

    def p_param__pointer_default(self, p):
        "param : type STAR ident ASSIGN STRING"
        p[0] = ast.FormalParamAST(self, p[1], p[3], p[5], "PTR")

    def p_param__default_number(self, p):
        "param : type ident ASSIGN NUMBER"
        p[0] = ast.FormalParamAST(self, p[1], p[2], p[4])

    def p_param__default_bool(self, p):
        "param : type ident ASSIGN LIT_BOOL"
        p[0] = ast.FormalParamAST(self, p[1], p[2], p[4])

    def p_param__default_string(self, p):
        "param : type ident ASSIGN STRING"
        p[0] = ast.FormalParamAST(self, p[1], p[2], p[4])

    # Type
    def p_types__multiple(self, p):
        "types : type ',' types"
        p[0] = [p[1]] + p[3]

    def p_types__one(self, p):
        "types : type"
        p[0] = [p[1]]

    def p_types__empty(self, p):
        "types : empty"
        p[0] = []

    def p_typestr__multi(self, p):
        "typestr : typestr DOUBLE_COLON ident"
        p[0] = "%s::%s" % (p[1], p[3])

    def p_typestr__single(self, p):
        "typestr : ident"
        p[0] = p[1]

    def p_type__one(self, p):
        "type : typestr"
        p[0] = ast.TypeAST(self, p[1])

    def p_void(self, p):
        "void : VOID"
        p[0] = ast.TypeAST(self, p[1])

    # Idents and lists
    def p_idents__braced(self, p):
        "idents : '{' identx '}'"
        p[0] = p[2]

    def p_idents__bare(self, p):
        "idents : ident"
        p[0] = [p[1]]

    def p_identx__multiple_1(self, p):
        """identx : ident SEMI identx
        | ident ',' identx"""
        p[0] = [p[1]] + p[3]

    def p_identx__multiple_2(self, p):
        "identx : ident identx"
        p[0] = [p[1]] + p[2]

    def p_identx__single(self, p):
        "identx : empty"
        p[0] = []

    def p_ident(self, p):
        "ident : IDENT"
        p[0] = p[1]

    def p_ident_or_star(self, p):
        """ident_or_star : ident
        | STAR"""
        p[0] = p[1]

    # Pair and pair lists
    def p_pairs__list(self, p):
        "pairs : ',' pairsx"
        p[0] = p[2]

    def p_pairs__empty(self, p):
        "pairs : empty"
        p[0] = ast.PairListAST(self)

    def p_pairsx__many(self, p):
        "pairsx : pair ',' pairsx"
        p[0] = p[3]
        p[0].addPair(p[1])

    def p_pairsx__one(self, p):
        "pairsx : pair"
        p[0] = ast.PairListAST(self)
        p[0].addPair(p[1])

    def p_pair__assign(self, p):
        """pair : ident '=' STRING
        | ident '=' ident
        | ident '=' NUMBER"""
        p[0] = ast.PairAST(self, p[1], p[3])

    def p_pair__literal(self, p):
        "pair : STRING"
        p[0] = ast.PairAST(self, "short", p[1])

    # Below are the rules for action descriptions
    def p_statements__inner(self, p):
        "statements : '{' statements_inner '}'"
        p[0] = ast.StatementListAST(self, p[2])

    def p_statements__none(self, p):
        "statements : '{' '}'"
        p[0] = ast.StatementListAST(self, [])

    def p_statements_inner__many(self, p):
        "statements_inner : statement statements_inner"
        p[0] = [p[1]] + p[2]

    def p_statements_inner__one(self, p):
        "statements_inner : statement"
        p[0] = [p[1]]

    def p_exprs__multiple(self, p):
        "exprs : expr ',' exprs"
        p[0] = [p[1]] + p[3]

    def p_exprs__one(self, p):
        "exprs : expr"
        p[0] = [p[1]]

    def p_exprs__empty(self, p):
        "exprs : empty" ""
        p[0] = []

    def p_statement__expression(self, p):
        "statement : expr SEMI"
        p[0] = ast.ExprStatementAST(self, p[1])

    def p_statement__assign(self, p):
        "statement : expr ASSIGN expr SEMI"
        p[0] = ast.AssignStatementAST(self, p[1], p[3])

    def p_statement__enqueue(self, p):
        "statement : ENQUEUE '(' var ',' type ')' statements"
        p[0] = ast.EnqueueStatementAST(self, p[3], p[5], None, p[7])

    def p_statement__enqueue_latency(self, p):
        "statement : ENQUEUE '(' var ',' type ',' expr ')' statements"
        p[0] = ast.EnqueueStatementAST(self, p[3], p[5], p[7], p[9])

    def p_statement__defer_enqueueing(self, p):
        "statement : DEFER_ENQUEUEING '(' var ',' type ')' statements"
        p[0] = ast.DeferEnqueueingStatementAST(self, p[3], p[5], p[7])

    def p_statement__stall_and_wait(self, p):
        "statement : STALL_AND_WAIT '(' var ',' var ')' SEMI"
        p[0] = ast.StallAndWaitStatementAST(self, p[3], p[5])

    def p_statement__wakeup_port(self, p):
        "statement : WAKEUP_PORT '(' var ',' var ')' SEMI"
        p[0] = ast.WakeupPortStatementAST(self, p[3], p[5])

    def p_statement__peek(self, p):
        "statement : PEEK '(' var ',' type pairs ')' statements"
        p[0] = ast.PeekStatementAST(self, p[3], p[5], p[6], p[8], "peek")

    def p_statement__check_allocate(self, p):
        "statement : CHECK_ALLOCATE '(' var ')' SEMI"
        p[0] = ast.CheckAllocateStatementAST(self, p[3])

    def p_statement__check_next_cycle(self, p):
        "statement : CHECK_NEXT_CYCLE '(' ')' SEMI"
        p[0] = ast.CheckNextCycleAST(self)

    def p_statement__check_stop(self, p):
        "statement : CHECK_STOP_SLOTS '(' var ',' STRING ',' STRING ')' SEMI"
        p[0] = ast.CheckStopStatementAST(self, p[3], p[5], p[7])

    def p_statement__check_probe(self, p):
        "statement : CHECK_PROBE '(' var ',' var ')' SEMI"
        p[0] = ast.CheckProbeStatementAST(self, p[3], p[5])

    def p_statement__return(self, p):
        "statement : RETURN expr SEMI"
        p[0] = ast.ReturnStatementAST(self, p[2])

    def p_statement__if(self, p):
        "statement : if_statement"
        p[0] = p[1]

    def p_if_statement__if(self, p):
        "if_statement : IF '(' expr ')' statements"
        p[0] = ast.IfStatementAST(self, p[3], p[5], None)

    def p_if_statement__if_else(self, p):
        "if_statement : IF '(' expr ')' statements ELSE statements"
        p[0] = ast.IfStatementAST(self, p[3], p[5], p[7])

    def p_statement__if_else_if(self, p):
        "if_statement : IF '(' expr ')' statements ELSE if_statement"
        p[0] = ast.IfStatementAST(
            self, p[3], p[5], ast.StatementListAST(self, p[7])
        )

    def p_expr__static_cast(self, p):
        "aexpr : STATIC_CAST '(' type ',' expr ')'"
        p[0] = ast.StaticCastAST(self, p[3], "ref", p[5])

    def p_expr__static_cast_ptr(self, p):
        "aexpr : STATIC_CAST '(' type ',' STRING ',' expr ')'"
        p[0] = ast.StaticCastAST(self, p[3], p[5], p[7])

    def p_expr__var(self, p):
        "aexpr : var"
        p[0] = p[1]

    def p_expr__localvar(self, p):
        "aexpr : type ident"
        p[0] = ast.LocalVariableAST(self, p[1], p[2])

    def p_expr__literal(self, p):
        "aexpr : literal"
        p[0] = p[1]

    def p_expr__enumeration(self, p):
        "aexpr : enumeration"
        p[0] = p[1]

    def p_expr__func_call(self, p):
        "aexpr : ident '(' exprs ')'"
        p[0] = ast.FuncCallExprAST(self, p[1], p[3])

    def p_expr__new(self, p):
        "aexpr : NEW type"
        p[0] = ast.NewExprAST(self, p[2])

    def p_expr__null(self, p):
        "aexpr : OOD"
        p[0] = ast.OodAST(self)

    def p_expr__member(self, p):
        "aexpr : aexpr DOT ident"
        p[0] = ast.MemberExprAST(self, p[1], p[3])

    def p_expr__member_method_call(self, p):
        "aexpr : aexpr DOT ident '(' exprs ')'"
        p[0] = ast.MemberMethodCallExprAST(
            self, p[1], ast.FuncCallExprAST(self, p[3], p[5])
        )

    def p_expr__member_method_call_lookup(self, p):
        "aexpr : aexpr '[' exprs ']'"
        p[0] = ast.MemberMethodCallExprAST(
            self, p[1], ast.FuncCallExprAST(self, "lookup", p[3])
        )

    def p_expr__class_method_call(self, p):
        "aexpr : type DOUBLE_COLON ident '(' exprs ')'"
        p[0] = ast.ClassMethodCallExprAST(
            self, p[1], ast.FuncCallExprAST(self, p[3], p[5])
        )

    def p_expr__aexpr(self, p):
        "expr : aexpr"
        p[0] = p[1]

    def p_expr__binary_op(self, p):
        """expr : expr STAR  expr
        | expr SLASH expr
        | expr MOD   expr
        | expr PLUS  expr
        | expr DASH  expr
        | expr LT    expr
        | expr GT    expr
        | expr LE    expr
        | expr GE    expr
        | expr EQ    expr
        | expr NE    expr
        | expr AND   expr
        | expr OR    expr
        | expr RIGHTSHIFT expr
        | expr LEFTSHIFT  expr"""
        p[0] = ast.InfixOperatorExprAST(self, p[1], p[2], p[3])

    # FIXME - unary not
    def p_expr__unary_op(self, p):
        """expr : NOT expr
        | INCR expr
        | DECR expr
        | DASH expr %prec UMINUS"""
        p[0] = ast.PrefixOperatorExprAST(self, p[1], p[2])

    def p_expr__parens(self, p):
        "aexpr : '(' expr ')'"
        p[0] = p[2]

    def p_expr__is_valid_ptr(self, p):
        "aexpr : IS_VALID '(' expr ')'"
        p[0] = ast.IsValidPtrExprAST(self, p[3], True)

    def p_expr__is_invalid_ptr(self, p):
        "aexpr : IS_INVALID '(' expr ')'"
        p[0] = ast.IsValidPtrExprAST(self, p[3], False)

    def p_literal__string(self, p):
        "literal : STRING"
        p[0] = ast.LiteralExprAST(self, p[1], "std::string")

    def p_literal__number(self, p):
        "literal : NUMBER"
        p[0] = ast.LiteralExprAST(self, p[1], "int")

    def p_literal__float(self, p):
        "literal : FLOATNUMBER"
        p[0] = ast.LiteralExprAST(self, p[1], "int")

    def p_literal__bool(self, p):
        "literal : LIT_BOOL"
        p[0] = ast.LiteralExprAST(self, p[1], "bool")

    def p_enumeration(self, p):
        "enumeration : ident ':' ident"
        p[0] = ast.EnumExprAST(self, ast.TypeAST(self, p[1]), p[3])

    def p_var(self, p):
        "var : ident"
        p[0] = ast.VarExprAST(self, p[1])
